package me.arvin.qa.jmeter.plugins.dependscontroller;

import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Font;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;

import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTree;
import javax.swing.SwingConstants;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeCellRenderer;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.DefaultTreeSelectionModel;
import javax.swing.tree.TreeNode;
import javax.swing.tree.TreePath;
import javax.swing.tree.TreeSelectionModel;

import org.apache.jmeter.control.Controller;
import org.apache.jmeter.control.ReplaceableController;
import org.apache.jmeter.control.TestFragmentController;
import org.apache.jmeter.control.TransactionController;
import org.apache.jmeter.control.gui.AbstractControllerGui;
import org.apache.jmeter.gui.GuiPackage;
import org.apache.jmeter.gui.tree.JMeterTreeNode;
import org.apache.jmeter.samplers.Sampler;
import org.apache.jmeter.testelement.TestElement;
import org.apache.jmeter.testelement.TestPlan;
import org.apache.jmeter.testelement.property.JMeterProperty;
import org.apache.jmeter.testelement.property.NullProperty;
import org.apache.jmeter.threads.AbstractThreadGroup;
import org.apache.jmeter.util.JMeterUtils;
import org.apache.jorphan.gui.layout.VerticalLayout;

public class DependsControllerGui extends AbstractControllerGui implements ActionListener {
	private static final long serialVersionUID = -4195441608252523573L;

	private static final TreeNode[] EMPTY_TREE_NODES = new TreeNode[0];

	private LinkedHashSet<JMeterTreeNode> selecteds = new LinkedHashSet<>();

	private LinkedHashSet<String> dependsList;

	private final JTree dependsRelationTree;

	private final JLabel warningLabel;

	private JButton expandButton;

	public DependsControllerGui() {
		dependsRelationTree = new JTree(new DefaultTreeModel(new DefaultMutableTreeNode()));
		dependsRelationTree.setCellRenderer(new DependsControllerCellRenderer());
		dependsRelationTree.setSelectionModel(new DependsControllerTreeSelectionModel());

		ImageIcon image = JMeterUtils.getImage("warning.png");
		warningLabel = new JLabel("", image, SwingConstants.LEFT); // $NON-NLS-1$
		warningLabel.setForeground(Color.RED);
		Font font = warningLabel.getFont();
		warningLabel.setFont(new Font(font.getFontName(), Font.BOLD, (int) (font.getSize() * 1.1)));
		warningLabel.setVisible(false);

		init();

		dependsRelationTree.addTreeSelectionListener(evt -> {
			warningLabel.setVisible(false);
			expandButton.setEnabled(true);
		});
		dependsRelationTree.addMouseListener(new MouseListener() {

			@Override
			public void mouseReleased(MouseEvent e) {}

			@Override
			public void mousePressed(MouseEvent e) {
				if (e.getButton() == MouseEvent.BUTTON3) {
					dependsRelationTree.clearSelection();
					selecteds.clear();
				}
			}

			@Override
			public void mouseExited(MouseEvent e) {}

			@Override
			public void mouseEntered(MouseEvent e) {

			}

			@Override
			public void mouseClicked(MouseEvent e) {

			}
		});
		DependsManageTreeLisener.addToTree();
	}

	@Override
	public String getLabelResource() {
		return this.getClass().getSimpleName();
	}

	@Override
	public String getStaticLabel() {
		return "依赖控制器";
	}

	@Override
	public void configure(TestElement el) {
		clearGui();
		super.configure(el);
		dependsList = ((DependsController) el).getDependsRelationsProperty().getDepends();
		reinitialize(el);
	}

	@Override
	public TestElement createTestElement() {
		DependsController dc = new DependsController();
		super.configureTestElement(dc);
		modifyTestElement(dc);
		return dc;
	}

	@Override
	public void modifyTestElement(TestElement element) {
		super.configureTestElement(element);

		selecteds.clear();
		TreePath[] selectionPaths = dependsRelationTree.getSelectionPaths();

		if (selectionPaths != null) {
			for (TreePath treePath : selectionPaths) {
				DefaultMutableTreeNode lastSelected = (DefaultMutableTreeNode) treePath.getLastPathComponent();
				JMeterTreeNode tn = null;
				if (lastSelected != null && lastSelected.getUserObject() instanceof JMeterTreeNode) {
					tn = (JMeterTreeNode) lastSelected.getUserObject();
				}
				if (tn != null && isTestElementAllowed(tn.getTestElement())) {
					selecteds.add(tn);
				}
			}
		}
		((DependsController) element).setDependsRelations(selecteds);

	}

	static boolean isTestElementAllowed(TestElement testElement) {
		if (testElement == null) {
			return false;
		}
		if (testElement instanceof Sampler) {
			return true;
		}
		if (testElement instanceof TransactionController
				&& ((TransactionController) testElement).isGenerateParentSample()) {
			return true;
		}
		return false;
	}

	private void init() {
		setLayout(new VerticalLayout(5, VerticalLayout.BOTH, VerticalLayout.TOP));
		setBorder(makeBorder());
		add(makeTitlePanel());
		ImageIcon warningImage = JMeterUtils.getImage("warning.png");
		JLabel label = new JLabel("警告：请勿将依赖控制器放入While控制器下", warningImage, SwingConstants.LEFT); // $NON-NLS-1$
		label.setForeground(Color.RED);
		Font font = label.getFont();
		label.setFont(new Font(font.getFontName(), Font.BOLD, (int) (font.getSize() * 1.5)));
		add(label);

		JPanel modulesPanel = new JPanel();

		expandButton = new JButton(JMeterUtils.getResString("find_target_element"));
		expandButton.addActionListener(this);

		modulesPanel.add(expandButton);
		modulesPanel.add(expandButton);

		modulesPanel.setLayout(new BoxLayout(modulesPanel, BoxLayout.Y_AXIS));
		modulesPanel.add(Box.createRigidArea(new Dimension(0, 5)));

		JLabel nodesLabel = new JLabel("提示：请选中依赖项（ctrl+鼠标左键单击可多选，右键清除所有选中项）");
		modulesPanel.add(nodesLabel);
		modulesPanel.add(warningLabel);
		this.add(modulesPanel);

		JPanel treePanel = new JPanel();
		treePanel.setLayout(new FlowLayout(FlowLayout.LEFT));
		treePanel.add(dependsRelationTree);
		this.add(treePanel);
	}

	private TreeNode[] findPathInTreeModel(int level, TreeNode[] testPlanPath, DefaultMutableTreeNode parent) {
		if (level >= testPlanPath.length) {
			return EMPTY_TREE_NODES;
		}
		int childCount = parent.getChildCount();
		JMeterTreeNode searchedTreeNode = (JMeterTreeNode) testPlanPath[level];

		for (int i = 0; i < childCount; i++) {
			DefaultMutableTreeNode child = (DefaultMutableTreeNode) parent.getChildAt(i);
			JMeterTreeNode childUserObj = (JMeterTreeNode) child.getUserObject();

			if (childUserObj.equals(searchedTreeNode)) {
				if (level == (testPlanPath.length - 1)) {
					return child.getPath();
				} else {
					return findPathInTreeModel(level + 1, testPlanPath, child);
				}
			}
		}
		return EMPTY_TREE_NODES;
	}

	private void focusSelectedOnTree(JMeterTreeNode selected) {
		TreeNode[] path = selected.getPath();
		TreeNode[] filteredPath = new TreeNode[path.length - 1];

		for (int i = 1; i < path.length; i++) {
			filteredPath[i - 1] = path[i];
		}
		DefaultMutableTreeNode root = (DefaultMutableTreeNode) dependsRelationTree.getModel().getRoot();
		TreeNode[] dmtnPath = findPathInTreeModel(1, filteredPath, root);
		if (dmtnPath.length > 0) {
			TreePath treePath = new TreePath(dmtnPath);
			dependsRelationTree.addSelectionPath(treePath);
			dependsRelationTree.scrollPathToVisible(treePath);
		}
	}

	private void reinitialize(TestElement el) {
		((DefaultMutableTreeNode) dependsRelationTree.getModel().getRoot()).removeAllChildren();

		GuiPackage gp = GuiPackage.getInstance();

		if (gp != null) {

			JMeterTreeNode root = (JMeterTreeNode) GuiPackage.getInstance().getTreeModel().getRoot();

			try {
				buildTreeNodeModel(root, 0, null, el);
			} catch (RuntimeException e) {}

			((DefaultTreeModel) dependsRelationTree.getModel()).nodeStructureChanged(root);
		}

		for (JMeterTreeNode selected : selecteds) {
			focusSelectedOnTree(selected);
		}
	}

	private void buildTreeNodeModel(JMeterTreeNode node, int level, DefaultMutableTreeNode parent,
			TestElement current) {

		if (node != null) {
			for (int i = 0; i < node.getChildCount(); i++) {
				JMeterTreeNode cur = (JMeterTreeNode) node.getChildAt(i);
				TestElement te = cur.getTestElement();

				if (te instanceof TestPlan) {
					((DefaultMutableTreeNode) dependsRelationTree.getModel().getRoot()).setUserObject(cur);
					buildTreeNodeModel(cur, level, (DefaultMutableTreeNode) dependsRelationTree.getModel().getRoot(),
							current);
				} else if (te instanceof TestFragmentController || te instanceof AbstractThreadGroup) {
					DefaultMutableTreeNode newNode = new DefaultMutableTreeNode(cur);
					parent.add(newNode);
					buildTreeNodeModel(cur, level + 1, newNode, current);
				} else if (te instanceof Controller && !(te instanceof ReplaceableController)) {// 忽略模块控制器
					if (te.hashCode() == current.hashCode()) {
						throw new RuntimeException();
					}
					DefaultMutableTreeNode newNode = new DefaultMutableTreeNode(cur);
					parent.add(newNode);
					checkDepends(cur);
					buildTreeNodeModel(cur, level + 1, newNode, current);
				} else if (te instanceof Sampler) {
					DefaultMutableTreeNode newNode = new DefaultMutableTreeNode(cur);
					parent.add(newNode);
					checkDepends(cur);
				}
			}
		}
	}

	private void checkDepends(JMeterTreeNode cur) {
		JMeterProperty prop = cur.getTestElement().getProperty(DependsController.DependsRelationList);
		if (!(prop instanceof NullProperty)) {
			String id = ((DependsRelationProperty) prop).getId();
			if (dependsList.contains(id)) {
				selecteds.add(cur);
			}
		}
	}

	/** {@inheritDoc}} */
	@Override
	public void clearGui() {
		super.clearGui();
		dependsRelationTree.clearSelection();
		selecteds.clear();
		dependsList = null;
	}

	@Override
	public void actionPerformed(ActionEvent e) {
		if (e.getSource() == expandButton) {
			JMeterTreeNode tn = null;

			if (!dependsRelationTree.isSelectionEmpty()) {
				TreePath[] selectionPaths = dependsRelationTree.getSelectionPaths();

				for (TreePath selectionPath : selectionPaths) {
					DefaultMutableTreeNode selected = (DefaultMutableTreeNode) selectionPath.getLastPathComponent();

					if (selected != null && selected.getUserObject() instanceof JMeterTreeNode) {
						tn = (JMeterTreeNode) selected.getUserObject();
					}
					if (tn != null) {
						TreePath treePath = new TreePath(tn.getPath());
						// changing selection in a test plan tree
						GuiPackage.getInstance().getTreeListener().getJTree().addSelectionPath(treePath);
						// expanding tree to make referenced element visible in test plan tree
						GuiPackage.getInstance().getTreeListener().getJTree().scrollPathToVisible(treePath);
					}
				}
			}
		}
	}

	private static class DependsControllerCellRenderer extends DefaultTreeCellRenderer {

		private static final long serialVersionUID = 1129098620102526299L;

		/**
		 * @see javax.swing.tree.DefaultTreeCellRenderer#getTreeCellRendererComponent(javax.swing.JTree,
		 *      java.lang.Object, boolean, boolean, boolean, int, boolean)
		 */
		@Override
		public Component getTreeCellRendererComponent(JTree tree, Object value, boolean selected, boolean expanded,
				boolean leaf, int row, boolean hasFocus) {
			JMeterTreeNode node = (JMeterTreeNode) ((DefaultMutableTreeNode) value).getUserObject();
			if (node != null) {
				super.getTreeCellRendererComponent(tree, node.getName(), selected, expanded, leaf, row, hasFocus);
				// print same icon as in test plan tree
				boolean enabled = node.isEnabled();
				ImageIcon icon = node.getIcon(enabled);
				if (icon != null) {
					if (enabled) {
						setIcon(icon);
					} else {
						setDisabledIcon(icon);
					}
				} else if (!enabled) { // i.e. no disabled icon found
					// Must therefore set the enabled icon so there is at least some icon
					icon = node.getIcon();
					if (icon != null) {
						setIcon(icon);
					}
				}
				setEnabled(enabled);
			}
			return this;
		}
	}

	private static class DependsControllerTreeSelectionModel extends DefaultTreeSelectionModel {

		private static final long serialVersionUID = 4062816201792954617L;

		DependsControllerTreeSelectionModel() {
			setSelectionMode(TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION);
		}

		private boolean isSelectedPathAllowed(DefaultMutableTreeNode lastSelected) {
			JMeterTreeNode tn = null;
			if (lastSelected != null && lastSelected.getUserObject() instanceof JMeterTreeNode) {
				tn = (JMeterTreeNode) lastSelected.getUserObject();
			}
			return tn != null && DependsControllerGui.isTestElementAllowed(tn.getTestElement());
		}

		@Override
		public void setSelectionPath(TreePath path) {
			DefaultMutableTreeNode lastSelected = (DefaultMutableTreeNode) path.getLastPathComponent();

			if (isSelectedPathAllowed(lastSelected)) {
				clearSelection();
				super.setSelectionPath(path);
			}
		}

		@Override
		public void setSelectionPaths(TreePath[] pPaths) {
			List<TreePath> pathList = new ArrayList<>();
			for (TreePath path : pPaths) {
				DefaultMutableTreeNode lastSelected = (DefaultMutableTreeNode) path.getLastPathComponent();
				if (isSelectedPathAllowed(lastSelected)) {
					pathList.add(path);
				}
			}
			clearSelection();
			super.setSelectionPaths(pathList.toArray(new TreePath[pathList.size()]));
		}

		@Override
		public void addSelectionPath(TreePath path) {
			DefaultMutableTreeNode lastSelected = (DefaultMutableTreeNode) path.getLastPathComponent();
			if (isSelectedPathAllowed(lastSelected)) {
				super.addSelectionPath(path);
			}
		}

		@Override
		public void addSelectionPaths(TreePath[] paths) {

			List<TreePath> pathList = new ArrayList<>();
			for (TreePath path : paths) {
				DefaultMutableTreeNode lastSelected = (DefaultMutableTreeNode) path.getLastPathComponent();
				if (isSelectedPathAllowed(lastSelected)) {
					pathList.add(path);
				}
			}

			super.addSelectionPaths(pathList.toArray(new TreePath[pathList.size()]));
		}
	}
}
